/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "spatial_native_event.h"

#include "spatial_awareness_common.h"

using namespace OHOS::Msdp;

SpatialNativeEvent::SpatialNativeEvent(napi_env env, napi_value thisVar)
{
    env_ = env;
    thisVarRef_ = nullptr;
    napi_create_reference(env, thisVar, 1, &thisVarRef_);
}

SpatialNativeEvent::~SpatialNativeEvent()
{
    for (auto iter = eventMap_.begin(); iter != eventMap_.end(); iter++) {
        for (auto listener : iter->second) {
            napi_delete_reference(env_, listener->handlerRef);
        }
        iter->second.clear();
    }
    napi_delete_reference(env_, thisVarRef_);
}

void SpatialNativeEvent::On(const std::string &eventMode, napi_value handler)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "SpatialNativeEvent On in for event: %{public}s", eventMode.c_str());
    auto listener = std::make_shared<SpatialNativeEventListener>();
    std::set<const std::shared_ptr<SpatialNativeEventListener>, classcomp> napiListenerSet;
    napi_create_reference(env_, handler, 1, &listener->handlerRef);
    auto modeIter = eventMap_.find(eventMode);
    if (modeIter == eventMap_.end()) {
        SPATIAL_LOGI(MODULE_JS_NAPI, "eventMode not found");
        auto ret = napiListenerSet.insert(listener);
        if (ret.second) {
            SPATIAL_LOGI(MODULE_JS_NAPI, "insert success");
        }
        eventMap_.insert(std::make_pair(eventMode, napiListenerSet));
    } else {
        SPATIAL_LOGI(MODULE_JS_NAPI, "eventMode found");
        auto ret = modeIter->second.insert(listener);
        if (ret.second) {
            SPATIAL_LOGI(MODULE_JS_NAPI, "insert success");
        }
    }
    SPATIAL_LOGI(MODULE_JS_NAPI, "eventMap_.size:%{public}d", eventMap_.size());
}

void SpatialNativeEvent::Off(const std::string &eventMode, napi_value handler)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "event: %{public}s", eventMode.c_str());
    napi_handle_scope scope = nullptr;
    napi_open_handle_scope(env_, &scope);
    if (scope == nullptr) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "scope is nullptr");
        return;
    }

    napi_ref handlerRef = nullptr;
    napi_create_reference(env_, handler, 1, &handlerRef);

    auto modeIter = eventMap_.find(eventMode);
    if (modeIter == eventMap_.end()) {
        return;
    }

    for (auto iter : modeIter->second) {
        if (iter->handlerRef == handlerRef) {
            napi_delete_reference(env_, iter->handlerRef);
            modeIter->second.erase(iter);
            return;
        }
    }

    if (modeIter->second.size() == 0) {
        eventMap_.erase(modeIter);
    }

    napi_close_handle_scope(env_, scope);
}

void SpatialNativeEvent::OnEvent(const std::string &eventMode, size_t argc, const napi_value *argv)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "OnEvent for %{public}s", eventMode.c_str());
    napi_handle_scope scope = nullptr;
    napi_open_handle_scope(env_, &scope);
    if (scope == nullptr) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "scope is nullptr");
        return;
    }
    SPATIAL_LOGI(MODULE_JS_NAPI, "OnEvent eventMap_.size:%{public}d", eventMap_.size());
    auto modeIter = eventMap_.find(eventMode);
    if (modeIter == eventMap_.end()) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "eventType %{public}s not find", eventMode.c_str());
        return;
    }

    for (auto iter : modeIter->second) {
        napi_value thisVar = nullptr;
        napi_status status = napi_get_reference_value(env_, thisVarRef_, &thisVar);
        if (status != napi_ok) {
            SPATIAL_LOGE(MODULE_JS_NAPI,
                "napi_get_reference_value thisVar for %{public}s failed, status=%{public}d", eventMode.c_str(), status);
            return;
        }

        napi_value handler = nullptr;
        status = napi_get_reference_value(env_, iter->handlerRef, &handler);
        if (status != napi_ok) {
            SPATIAL_LOGE(MODULE_JS_NAPI,
                "napi_get_reference_value handler for %{public}s failed, status=%{public}d", eventMode.c_str(), status);
            return;
        }

        napi_value callResult = nullptr;
        status = napi_call_function(env_, thisVar, handler, argc, argv, &callResult);
        if (status != napi_ok) {
            SPATIAL_LOGE(MODULE_JS_NAPI,
                "napi_call_function for %{public}s failed, status=%{public}d", eventMode.c_str(), status);
            return;
        }
    }
    napi_close_handle_scope(env_, scope);
}